/**
 * 20220303:loading assets with both [classic url] and [dynamic import url]...jordia
 * 20220310:simplify [asset-urls] inputting，no longer support [array-input]
 * 20220417:[this.onLoaded->this._onLoaded] when no asset needs to load
 */
import {TextureLoader,CubeTextureLoader,AudioLoader,FileLoader} from 'three';
import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader';

const LOADER={
    "texture":{name:TextureLoader,prefix:['data:image/','.'], postfix:["jpg","jpeg","svg","png","tif"]},
    "gltf":{name:GLTFLoader,prefix:['.'],postfix:["glb"]},
    "audio":{name:AudioLoader,prefix:['.'],postfix:["mp3","ogg"]},
    "file":{name:FileLoader,prefix:['.'],postfix:["json","txt"]},
    "cubeTexture":{name:CubeTextureLoader,prefix:['data:image/','.'],postfix:[["jpg","jpeg","png"]]},
};

/**
 * define loadable type of assets ,select useable loader,and get loaded assets;
 * use [assets-load] to load assets;
 * see [assets-load.js] to find out how to set [load-infomation] 
 */
export default class AssetsLoader{

    //................................................public.......................................................

    /**
     * generate and return an assets-object
     * @param {Object}
     */
    get assets(){
        let ast={};
        for(let i=0;i<this._infos.length;i++){
            const info=this._infos[i];
            ast[info.name]=this._loadedAssets[i];
        }
        return ast;
    }

    /**
     * set callback for load-start
     * @param {function} func function()
     */
    set onLoadStart(func){
        this._onLoadStart=func||null;
    }

    /**
     * set callback for load-progress 
     * @param {function} func_info_prg function(asset-info,current-loaded-progress)
     */
    set onLoading(func_info_prg){
        this._onLoading=func_info_prg||null;
    }

    /**
     * set callback for load-error 
     * @param {function} func_err function(error-message)
     */
    set onError(func_err){
        this._onError=func_err||null;
    }

    /**
     * set callback for load-success 
     * @param {function} func function()
     */
    set onLoaded(func){
        this._onLoaded=func||null;
    }

    /**
     * (processed)url and postfix of asset
     * @param  {object} targets  {url}
     */
    setTargets(target){
        this._infos=[];
        let id=-1;
        for(let key in target){

            const typ=this._lookupTypeString(target[key]);
            const LoaderClass=LOADER[typ].name;
            if(!LoaderClass){
                console.warn('no loader was found to handle this type of asset:',target);
                continue;
            }
            id++;
            const loader=new LoaderClass();
            this._infos.push({
                id:id,
                name:key,
                url:target[key],
                loader:loader
            });
        }
        
        return this;
    }

    /**
     * start loading
     */
    startLoad(){
        if(this._isLoading)return;
        if((!this._infos)||(!this._infos.length)){
            if(this._onLoadStart)this._onLoadStart();
            if(this._onLoaded)this._onLoaded();//20220417
            return;
        }
        this._loadedAssets=[];
        this._total=this._infos.length;
        this._progress=0;
        this._progress={
            total:this._infos.length,
            current:new Float32Array(this._infos.length),
        };
        if(this._onLoadStart)this._onLoadStart();
        for(let i=0;i<this._infos.length;i++){
            this._loadInfo(this._infos[i]).then(
                (res)=>{
                    this._loadedAssets[i]=res;
                    this._checkLoaded();
                },
                (err)=>{
                    if(this._onError)this._onError(err);
                }
            );
        }

    }

    /**
     * get asset by name or index
     * @param {string|number} nameOrIndex  name-string or index-number
     */
    getAsset(nameOrIndex){
        if(typeof nameOrIndex==='number'){
            return this._loadedAssets[parseInt(nameOrIndex)];
        }else{
            for(let i=0;i<this._infos.length;i++){
                const info=this._infos[i];
                if(info.name===nameOrIndex)return this._loadedAssets[i];
            }
        }
        console.warn('no asset was found for given name/index!');
        return null;
    }

    //.....................................................private........................................................

    _lookupTypeString(str){
        for(let key in LOADER){
            const loader=LOADER[key];
            const rf=loader.prefix;
            const tf=loader.postfix;
            let filter=null;
            if(Array.isArray(str)){
                if(!Array.isArray(tf[0]))continue;
                for(let r of rf){
                    for(let t of tf[0]){
                        filter=r+t;
                        const reg=new RegExp(filter);
                        if(reg.test(str))return key;
                    }
                }
            }else{
                for(let r of rf){
                    for(let t of tf){
                        filter=r+t;
                        const reg=new RegExp(filter);
                        if(reg.test(str))return key;
                    }
                }
            }
        }
        return null;
    }
    _checkLoaded(){
        if(this._loadedAssets.length!==this._infos.length)return false;
        for(let as of this._loadedAssets){
            if(!as)return false;
        }
        if(this._onLoaded)this._onLoaded();
        return true;
    }
    _loadInfo(info){
        return new Promise((resolve,reject)=>{
            info.loader.load(info.url,
                (res)=>{
                    this._progress.current[info.id]=1;
                    this._onProgress(info);
                    resolve(res);
                },
                (e)=>{
                    this._progress.current[info.id]=e.total?e.loaded/e.total:0;
                    this._onProgress(info);
                },
                (err)=>{
                    reject(err);
                }
            );
        });
    }
    
    _onProgress(info){
        let prg=0;
        for(let i=0;i<this._progress.current.length;i++){
            prg+=this._progress.current[i];
        }
        prg=this._progress.total?prg/this._progress.total:1;
        if(this._onLoading)this._onLoading(info,prg);
    }

}